unit server;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Crt, lNet, tcpbuffers, fpstructs, fposUtils,
  IniFiles, serverUtils, fpreports;

type

{ TfpServer }

  TfpServer = class
   private
    FCon: TTCPConnection; // The server connection
   {  these are all events which happen on our server connection. They are called inside CallAction
      OnEr gets fired when a network error occurs.
      OnAc gets fired when a new connection is accepted on the server socket.
      OnRe gets fired when any of the server sockets receives new data.
      OnDs gets fired when any of the server sockets disconnects gracefully.
   }
    FWaiting: Boolean;
    FCurrentSocket: TLSocket;
    fSettings: TIniFile;
    fDataDirectory: TFilename;
    fSystemConfig: TFilename;
    fSaleRegister: TFilename;
    fEventLog: TFilename;
    fDefaultProfile: TFilename;
    fStarttime: TDateTime;
    procedure OnEr(const msg: string; aSocket: TLSocket);
    procedure OnAc(aSocket: TLSocket);
    procedure OnDs(aSocket: TLSocket);
    procedure ReceiveMessage( Sender: Tobject );
    procedure CheckServerConfig;
   public
    Port: Word;
    LastUpdate: longint;
    MessageList: TList;
    constructor Create;
    destructor Destroy; override;
    procedure Run; // main loop with CallAction
  end;

var
  TCP: TfpServer;


implementation


procedure TfpServer.OnEr ( const msg: string; aSocket: TLSocket ) ;
begin
  Writeln(msg, ' - connection with ', aSocket.PeerAddress);  // if error occured, write it explicitly
  RegisterEvent(-1, -1,  'TCP error: [' + aSocket.PeerAddress + '] ' + msg, fEventLog);
end;

procedure TfpServer.OnAc ( aSocket: TLSocket ) ;
begin
  Writeln('Connection accepted from ', aSocket.PeerAddress); // on accept, write whom we accepted
  RegisterEvent(-1, -1, aSocket.PeerAddress + ' connect', fEventLog);
end;

procedure TfpServer.OnDs ( aSocket: TLSocket ) ;
begin
  Writeln('Lost connection to ', aSocket.PeerAddress); // write info if connection was lost
  RegisterEvent(-1, -1, aSocket.PeerAddress + ' disconnect', fEventLog);
end;

procedure TfpServer.ReceiveMessage ( Sender: Tobject ) ;
var
  s: string;
  n: Integer;
  header : tfpHeader;
  summary: TfpSaleSummary;
  datatype: TfpDatatype;
  bytesread: integer;
  intData: Integer;
  dblData: double;
  SQL: string;
begin
  with Sender as TTCPBuffer do
          begin
            case header.MessageType of
                 // Sales Terminal Messages
                 fpmTerminalLogon: begin
                                     WriteLn('Logon terminalID=', header.TerminalID);
                                     RegisterEvent(header.TerminalID, -1, 'Logon Terminal #' + IntToStr(header.TerminalID) + ' at ' + Socket.PeerAddress, fEventLog);
                                     Data.Read(intData, SizeOf(intData));
                                     if intData>0
                                        then CheckSalesData(Fcon, fSystemConfig, header.TerminalID, intData, Socket);
                                     Data.Read(dblData, SizeOf(dblData));
                                     if dblData>0
                                        then CheckShiftData(Fcon, fSystemConfig, TTCPBuffer( Sender ), dblData);
                                     Data.Read(intData, SizeOf(intData));
                                     {if intData<LastUpdate
                                        then for intData := intData+1 to LastUpdate do
                                          SendUpdate( Fcon, aSocket, UPDATE_CONFIG, intData );} // NB: will not work!!

                                   end;
                 fpmRegisterTerminal: begin
                                        writeln('Register new terminal at ', Socket.PeerAddress);
                                        TryRegisterTerminal(Fcon, fSystemConfig, TTCPBuffer(Sender));
                                      end;
                 fpmRegisterSale: begin
                                    writeln('Register Sale terminal ', header.TerminalID);
                                    SetLength(SQL, header.datalength);
                                    bytesread := Data.Read(SQL[1], header.datalength);
                                    if bytesread=header.datalength then ExecuteSQL( fSaleRegister, SQL );
                                    writeln(SQL);
                                  end;
                 fpmStaffLogon: begin
                                    writeln('Staff Logon terminal ', header.TerminalID);
                                    SetLength(SQL, header.datalength);
                                    bytesread := Data.Read(SQL[1], header.datalength);
                                    if bytesread=header.datalength then
                                      begin
                                        s := Copy(SQL, 1, Pos(#3, SQL)-1);
                                        Delete(SQL, 1, Pos(#3, SQL));
                                        ExecuteSQL( fSaleRegister, SQL );
                                        RegisterEvent(header.TerminalID, -1, 'Staff id #' + s + ' start shift', fEventLog);
                                      end;
                                    writeln(SQL);
                                  end;
                 fpmStaffLogoff: begin
                                    writeln('Staff Logout terminal ', header.TerminalID);
                                    SetLength(SQL, header.datalength);
                                    bytesread := Data.Read(SQL[1], header.datalength);
                                    if bytesread=header.datalength then
                                      begin
                                        s := Copy(SQL, 1, Pos(#3, SQL)-1);
                                        Delete(SQL, 1, Pos(#3, SQL));
                                        ExecuteSQL( fSaleRegister, SQL );
                                        RegisterEvent(header.TerminalID, -1, 'Staff id #' + s + ' end shift', fEventLog);
                                      end;
                                    writeln(SQL);
                                  end;
                 // Admin messages
                 fpmSubmitConfigData: begin
                                         writeln('Update config data from terminal ', header.TerminalID);
                                           {case header.datatype of
                                                fpdTimeout:  ;
                                                fpdProductRef : begin
                                                                   writeln('datalength is', header.datalength);
                                                                   writeln(aSocket.Get(aProduct, SizeOf(aProduct)));
                                                                   writeln('Received update for product ', aProduct.id);
                                                                   UpdateProduct(fSystemConfig, aProduct);
                                                                 end;
                                           end;                       }
                                       if header.TerminalID=ADMIN_ID then
                                         begin
                                           Inc(LastUpdate);
                                           SendUpdate( Fcon, Socket, UPDATE_CONFIG, LastUpdate );
                                           fSettings.WriteInteger('Terminals', 'lastupdate', LastUpdate);
                                           ExecuteSQL(fSystemConfig, 'INSERT INTO Updates VALUES('
                                                                 + IntToStr(LastUpdate) + ','
                                                                 + DateTimeToJulianDateTimeAsString(Now) + ');');
                                           RegisterEvent(SERVER_ID, -1, 'General update #' + IntToStr(LastUpdate), fEventLog);
                                         end;
                 end;
                 fpmSQLQuery : begin
                                    writeln('SQLQuery');
                                    GetQuery(Fcon, TTCPBuffer( Sender ) );
                 end;
                 fpmExecSQL :
                       begin
                         write('ExecSQL: ');
                         SetLength(SQL, Header.datalength);
                         bytesread := Data.Read(SQL[1], header.datalength);
                         writeln(SQL);
                         if bytesread=header.datalength
                            then if ExecuteSQL(fSystemConfig, SQL)=0 then
                              if header.datatype=fpdSQLUpdate then
                                begin
                                  AddToUpdate(UPDATE_CONFIG, SQL);
                                  SendConfirm(Fcon, Socket); // TODO: send error messages
                                end;
                       end;
                 fpmRequestReport: GetReport(FCon, TTCPBuffer(Sender) );
            end;
            FCon.DeleteMessage( TTCPBuffer(Sender) );
          end;
end;

procedure TfpServer.CheckServerConfig;
begin
  fSystemConfig := fDataDirectory + SYSTEM_CONFIG;
  if not FileExists(fSystemConfig)
     then CreateSystemConfig(fSystemConfig);
  writeln ('fSystemConfig at: ', fSystemConfig);
  fSaleRegister := fDataDirectory + SALEREGISTER;
  if not FileExists(fSaleRegister)
     then CreateSalesRegister(fSaleRegister);

  fEventLog := fDataDirectory + EVENTLOG;
  if not FileExists(fEventLog)
     then CreateLogfile(fEventLog);

  fDefaultProfile := fDataDirectory + DEFAULT_PROFILE;
  if not FileExists(fDefaultProfile) then
     begin
       CreateProfile(fDefaultProfile, 'Default profile', 'Initial default profile');
       SetDefaultProfile(fDefaultProfile);
     end;
end;

constructor TfpServer.Create;
begin
  fDataDirectory := GetFirePOSConfigDir;
  if not DirectoryExists(fDataDirectory)
     then ForceDirectories(fDataDirectory);
  fSettings := TIniFile.Create(GetFirePOSConfigDir + 'fpserver.ini');
  writeln('Configuration in ', GetAppConfigDir(false));
  CheckServerConfig;
  SYSTEMDB := fSystemConfig;
  SALESDB := fSaleRegister;
  EVENTSDB := fEventLog;
  FCon := TTCPConnection.Create; // create new TCP connection
  Port := fSettings.ReadInteger('Server', 'port', DEFAULT_PORT);
  LastUpdate := fSettings.ReadInteger('Terminals', 'lastupdate', 0);
  Fcon.OnReceiveMessage := @ReceiveMessage;
  FCon.OnError := @OnEr;     // assign all callbacks
  FCon.OnDisconnect := @OnDs;
  FCon.Connection.OnAccept := @OnAc;
  FCon.Connection.Timeout := fSettings.ReadInteger('Server', 'timeout', 100);  // responsive enough, but won't hog cpu
  FCon.Connection.ReuseAddress := True;
end;

destructor TfpServer.Destroy;
begin
  FCon.Free; // free the TCP connection
  fSettings.Free;
  inherited Destroy;
end;

procedure TfpServer.Run;
var
  Quit: Boolean; // main loop control
  uptime: TDateTime;
  f: string;
begin
    Quit := false;
    FWaiting := true;
    fStarttime := Now;
    //fStarttime := StrToDate('01-04-13');
    if FCon.Connection.Listen(Port) then begin // if listen went ok
      Writeln('Server running!');
      RegisterEvent(-1, -1, 'Server started', fEventLog);
      Fcon.Connection.IterReset;
      Writeln('Press Esc to quit; r to restart');
      repeat
        FCon.CallAction; // eventize the lNet
        if Keypressed then // if user provided input
          case readkey of
           #27: quit := true; // if he pressed "escape" then quit
           'r': begin       // if he pressed 'r' then restart the server
                  Writeln('Restarting...');
                  RegisterEvent(-1, -1, 'Server restarted', fEventLog);
                  FCon.Connection.Disconnect;
                  FCon.Connection.Listen(Port);
                  Quit := false;
                end;
           'x': begin
                  SendUpdate(Fcon, nil, '14.upd', 14);

                end;
          end;
      until Quit; // until user quit
      writeln(Now-fStarttime);
      uptime := Now-fStarttime;
      if uptime<1
         then RegisterEvent(-1, -1, 'Server shut down. Uptime: ' + FormatDateTime('"0 days, "h "hours," n "minutes" ss "seconds"', Now-fStarttime), fEventLog)
         else  RegisterEvent(-1, -1, 'Server shut down. Uptime: ' + FormatDateTime('d "days, "h "hours," n "minutes" ss "seconds"', Now-fStarttime), fEventLog);
    end; // listen
end;


end.

